﻿using System.Collections.Generic;
using MugenMvvmToolkit.ViewModels;
#if NETFX_CORE
using Windows.UI.Xaml.Navigation;
#else
using System.Windows.Navigation;
#endif
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.Models;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;

namespace MugenMvvmToolkit.Infrastructure
{
    /// <summary>
    ///     Represents the operation result navigation policy, that clears the operation result callback during back
    ///     navigation.
    /// </summary>
    public class DefaultNavigationOperationResultPolicy : INavigationOperationResultPolicy
    {
        #region Nested type

        private struct OperationResultClosure
        {
            #region Fields

            private readonly IHasOperationResult _hasOperationResult;
            private readonly NavigationResult _navigationResult;

            #endregion

            #region Constructors

            /// <summary>
            ///     Initializes a new instance of the <see cref="OperationResultClosure" /> class.
            /// </summary>
            private OperationResultClosure(IHasOperationResult hasOperationResult, NavigationResult navigationResult)
            {
                _hasOperationResult = hasOperationResult;
                _navigationResult = navigationResult;
                var operationResultCallback = _hasOperationResult as IHasOperationResultCallback;
                if (operationResultCallback != null)
                    operationResultCallback.OperationResultChanged += OperationResultCallbackOnOperationResultChanged;
                var viewModel = hasOperationResult as IViewModel;
                if (viewModel != null)
                    viewModel.Disposed += OnDisposed;
            }

            #endregion

            #region Methods

            public static OperationResultClosure? Register(IHasOperationResult hasOperationResult,
                NavigationResult navigationResult)
            {
                if (navigationResult.IsAttached)
                {
                    return null;
                }
                navigationResult.IsAttached = true;
                return new OperationResultClosure(hasOperationResult, navigationResult);
            }

            private void OperationResultCallbackOnOperationResultChanged(
                IHasOperationResultCallback hasOperationResultCallback, bool? b)
            {
                Clear((IViewModel) hasOperationResultCallback, b);
            }

            private void OnDisposed(IViewModel obj)
            {
                Clear(obj, _hasOperationResult.OperationResult);
            }

            public void Clear(IViewModel viewModel, bool? result)
            {
                var operationResultCallback = viewModel as IHasOperationResultCallback;
                if (operationResultCallback != null)
                    operationResultCallback.OperationResultChanged -= OperationResultCallbackOnOperationResultChanged;
                viewModel.Disposed -= OnDisposed;
                if (_navigationResult != null)
                    _navigationResult.TryCompleteOperation(true, result, null);
            }

            #endregion
        }

        #endregion

        #region Fields

        private readonly Dictionary<IViewModel, List<OperationResultClosure>> _resultCallbacks;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="DefaultNavigationOperationResultPolicy" /> class.
        /// </summary>
        public DefaultNavigationOperationResultPolicy()
        {
            _resultCallbacks = new Dictionary<IViewModel, List<OperationResultClosure>>();
        }

        #endregion

        #region Implementation of INavigationOperationResultPolicy

        /// <summary>
        ///     Registers a callback to complete the result operation.
        /// </summary>
        /// <param name="targetViewModel">The navigated view model.</param>
        /// <param name="navigationResult">The current navigation result.</param>
        /// <param name="context">The current navigation context.</param>
        public void RegisterOperationResultCallback(IViewModel targetViewModel, INavigationResult navigationResult,
            INavigationContext context)
        {
            var result = (NavigationResult) navigationResult;
            var operationResult = targetViewModel as IHasOperationResult;
            if (operationResult == null)
                result.TryCompleteOperation(true, null, null);
            else
            {
                OperationResultClosure? callback = OperationResultClosure.Register(operationResult, result);
                if (callback == null)
                    return;
                List<OperationResultClosure> values;
                if (!_resultCallbacks.TryGetValue(targetViewModel, out values))
                {
                    values = new List<OperationResultClosure>();
                    _resultCallbacks[targetViewModel] = values;
                }
                values.Add(callback.Value);
            }
        }

        /// <summary>
        ///     Tries to clear the operation result callback.
        /// </summary>
        public void TryClearOperationResultCallback(IViewModel targetViewModel, INavigationContext context)
        {
            if (context.NavigationMode != NavigationMode.Back || targetViewModel == null)
                return;
            List<OperationResultClosure> values;
            if (!_resultCallbacks.TryGetValue(targetViewModel, out values))
                return;
            var hasOperationResult = (IHasOperationResult) targetViewModel;
            values.ForEach(closure => closure.Clear(targetViewModel, hasOperationResult.OperationResult));
            _resultCallbacks.Remove(targetViewModel);
        }

        /// <summary>
        ///     Removes the view model from cache.
        /// </summary>
        public bool Clear(IViewModel viewModel)
        {
            return _resultCallbacks.Remove(viewModel);
        }

        /// <summary>
        ///     Clears the all operation callbacks.
        /// </summary>
        public void Clear()
        {
            _resultCallbacks.Clear();
        }

        #endregion
    }
}